/* J. David's webserver */
/* This is a simple webserver.
 * Created November 1999 by J. David Blackstone.
 * CSE 4344 (Network concepts), Prof. Zeigler
 * University of Texas at Arlington
 */
/* This program compiles for Sparc Solaris 2.6.
 * To compile for Linux:
 *  1) Comment out the #include <pthread.h> line.
 *  2) Comment out the line that defines the variable newthread.
 *  3) Comment out the two lines that run pthread_create().
 *  4) Uncomment the line that runs accept_request().
 *  5) Remove -lsocket from the Makefile.
 */
#define _XOPEN_SOURCE
#include <arpa/inet.h>
#include <ctype.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "../conf/conf.h"
#include "../log/log.h"
#include "cgi.h"
#include "resp.h"
#include "util.h"

#define SESSION_MAX 32

#define ISspace(x)  isspace((int)(x))

void accept_request(int);
void serve_file(int, const char *);
int startup(unsigned short *);

/**********************************************************************/
/* A request has caused a call to accept() on the server port to
 * return.  Process the request appropriately.
 * Parameters: the socket connected to the client */
/**********************************************************************/
void accept_request(int client)
{
    char buf[1024];
    int numchars;
    char method[255];
    char url[255];
    char path[512];
    size_t i, j;
    struct stat st;
    int cgi = 0; /* becomes true if server decides this is a CGI
                  * program */
    char *query_string = NULL;

    numchars = recv_line(client, buf, sizeof(buf));
    i = 0;
    j = 0;
    while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) {
        method[i] = buf[j];
        i++;
        j++;
    }
    method[i] = '\0';

    if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {
        resp_501(client);
        return;
    }

    if (strcasecmp(method, "POST") == 0)
        cgi = 1;

    i = 0;
    while (ISspace(buf[j]) && (j < sizeof(buf)))
        j++;
    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) {
        url[i] = buf[j];
        i++;
        j++;
    }
    url[i] = '\0';

    if (strcasecmp(method, "GET") == 0) {
        query_string = url;
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
        if (*query_string == '?') {
            cgi = 1;
            *query_string = '\0';
            query_string++;
        }
    }

    sprintf(path, "htdocs%s", url);
    if (path[strlen(path) - 1] == '/')
        strcat(path, "index.html");
    if (stat(path, &st) == -1) {
        while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
            numchars = recv_line(client, buf, sizeof(buf));
        resp_404(client);
    } else {
        if (S_ISDIR(st.st_mode))
            strcat(path, "/index.html");
        if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
            cgi = 1;
        if (!cgi)
            serve_file(client, path);
        else
            exec_cgi(client, path, method, query_string);
    }

    close(client);
}

void serve_file(int client, const char *filename)
{
    FILE *resource = NULL;
    int numchars = 1;
    char buf[1024];

    buf[0] = 'A';
    buf[1] = '\0';
    while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
        numchars = recv_line(client, buf, sizeof(buf));

    resource = fopen(filename, "r");
    if (resource == NULL)
        resp_404(client);
    else {
        resp_200(client, filename);
        send_file(client, resource);
    }
    fclose(resource);
}

void get_ip_port(int sock, char *ip, unsigned short *port)
{
    struct sockaddr_in addr = { 0 };
    socklen_t socklen = sizeof(addr);
    if (getsockname(sock, (struct sockaddr *)&addr, &socklen) == -1)
        log_fatal("getsockname");

    if (inet_ntop(AF_INET, &addr.sin_addr, ip, INET_ADDRSTRLEN) == NULL)
        log_fatal("inet_ntop");

    *port = ntohs(addr.sin_port);
}

/**********************************************************************/
/* Parameters: pointer to variable containing the port to connect on
 * Returns: the socket */
/**********************************************************************/
int start_server(const char *ip, unsigned short port)
{
    int sock = 0;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        log_fatal("socket");

    int _opt = SO_REUSEADDR;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &_opt, sizeof(_opt));

    struct sockaddr_in addr = { 0 };
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = (ip == NULL) ? htonl(INADDR_ANY) : inet_addr(ip);

    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
        log_fatal("bind");

    if (listen(sock, 5) < 0)
        log_fatal("listen");

    return (sock);
}

void poll_sock()
{
}

/**********************************************************************/
int main(void)
{
    int srv_sock = -1;
    int cli_sock = -1;
    unsigned short port = 0;
    struct sockaddr_in cli_addr;
    socklen_t socklen = sizeof(cli_addr);
    char *ip = "0.0.0.0";
    char ip_str[INET_ADDRSTRLEN] = { 0 };

    srv_sock = start_server(ip, port);

    get_ip_port(srv_sock, ip_str, &port);

    if (strcmp(ip_str, "0.0.0.0") == 0)
        log_info("* Running, you can visit: http://%s:%d\n", "127.0.0.1", port);
    else
        log_info("* Running on: http://%s:%d\n", ip_str, port);

    for (;;) {
        cli_sock = accept(srv_sock, (struct sockaddr *)&cli_addr, &socklen);
        if (cli_sock == -1)
            log_fatal("accept");
        accept_request(cli_sock);
    }

    close(srv_sock);

    return (0);
}
